package com.lock.redisLocks.support;

import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.configuration.HierarchicalConfiguration.Node;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.configuration.tree.ConfigurationNode;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lock.redisLocks.RedisHelper.JedisPoolWraper;
import com.lock.redisLocks.RedisHelper.ProviderConfigType;
import com.lock.redisLocks.RedisHelper.RedisConfigType;

import io.lettuce.core.AbstractRedisClient;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.support.ConnectionPoolSupport;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class RedisConfigHelper {
	private final static Logger log = LoggerFactory.getLogger(RedisConfigHelper.class);

	static final String VERSION_NAM = "version";
	static final String VERSION_VAL_3_0_0 = "3.0.0";
	static final String VERSION_VAL_2_0_0 = "2.0.0";
	static String version = VERSION_VAL_2_0_0;
	static String DEFAULT_POOL_NAME = null;

	/**
	 *
	 */
	public static synchronized TwoTuple<String, Map<String, JedisPoolWraper>> initConfig() {
		try {
			XMLConfiguration config = new XMLConfiguration();
			config.setEncoding("UTF-8");
			config.setDelimiterParsingDisabled(false);
			config.setThrowExceptionOnMissing(true);
			config.load("redis2s.xml");
			{
				Node node = config.getRoot();
				for (ConfigurationNode confNode : node.getAttributes()) {
					log.trace("confNode.getName():{}", confNode.getName());
					log.trace("confNode.getValue():{}", confNode.getValue());
					if (StringUtils.equals(confNode.getName(), VERSION_NAM)) {
						version = (String) confNode.getValue();
					}
				}
				log.trace("root:{}, version:{}", node.getName(), version);
			}
			TwoTuple<String, Map<String, JedisPoolWraper>> tuple = null;
			if (VERSION_VAL_3_0_0.equals(version)) {
				tuple = initConfigV3_0_0(config);

			} else {
				tuple = initConfigV2_0_0(config);

			} // end of conf ver 2.0.0

			DEFAULT_POOL_NAME = tuple.getItem1();
			return tuple;

		} catch (Throwable exc) {
			RuntimeException e = new RuntimeException("Init JedisPoolName...ERROR", exc);
			log.error("", e);
			throw e;
		} finally {
			log.info("DEFAULT_POOL_NAME={} ", DEFAULT_POOL_NAME);
		}
	}

	/**
	 * 
	 * @param config
	 * @return
	 */
	private static TwoTuple<String, Map<String, JedisPoolWraper>> initConfigV3_0_0(XMLConfiguration config) {
		String DEFAULT_POOL_NAME = null;
		Map<String, JedisPoolWraper> pools = new HashMap<>();
		String deffaultPoolName = config.getString("default-connection-pool");
		if (StringUtils.isNotBlank(deffaultPoolName)) {
			log.trace("DEFAULT_POOL_NAME={}", deffaultPoolName);
			DEFAULT_POOL_NAME = deffaultPoolName;
		}
		int connPoolQty = config.getMaxIndex("connection-pool") + 1;
		log.trace("ConnPoolQty:{}", connPoolQty);
		if (connPoolQty <= 0) {
			DEFAULT_POOL_NAME = null;
			throw new RuntimeException("connection-pool is null");
		}

		for (int i = 0; i < connPoolQty; i++) {
			String poolName = config.getString("connection-pool(" + i + ")[@name]", "");
			if (StringUtils.isBlank(poolName)) {
				clearPool(pools);
				throw new IllegalArgumentException("poolName can not be empty");
			}
			if (pools.containsKey(poolName)) {
				clearPool(pools);
				throw new IllegalArgumentException("duplicated poolName in config :" + poolName);
			}
			if (i == 0 && StringUtils.isBlank(DEFAULT_POOL_NAME)) {
				log.trace("DEFAULT_POOL_NAME={}", poolName);
				DEFAULT_POOL_NAME = poolName;
			}
			//
			String poolType = config.getString("connection-pool(" + i + ")[@type]", "");
			RedisConfigType redisConfigType = RedisConfigType.getFromType(poolType);
			//
			String poolProvider = config.getString("connection-pool(" + i + ")[@provider]", "");
			ProviderConfigType providerConfigType = ProviderConfigType.getFromType(poolProvider);
			//
			String usePoolStr = config.getString("connection-pool(" + i + ")[@usePool]", "");
			boolean usePool = Boolean.parseBoolean(usePoolStr);
			if (ProviderConfigType.JEDIS.equals(providerConfigType) && usePool == false) {
				throw new IllegalArgumentException("Provider JEDIS only support usePool is true, poolName in config :" + poolName);
			} else if (ProviderConfigType.LETTUCE.equals(providerConfigType) && usePool == true) {
				throw new IllegalArgumentException("Provider LETTUCE only support usePool is false, poolName in config :" + poolName);
			}

			GenericObjectPoolConfig<?> poolConfig = null;
			if (usePool) {
				poolConfig = new GenericObjectPoolConfig<>();
				poolConfig.setMaxTotal(config.getInt("connection-pool(" + i + ").max-active", -1));
				poolConfig.setMinIdle(config.getInt("connection-pool(" + i + ").min-idle", 5));
				poolConfig.setMaxIdle(config.getInt("connection-pool(" + i + ").max-idle", 10));
				poolConfig.setMaxWaitMillis(config.getInt("connection-pool(" + i + ").max-wait", 5000));
				poolConfig.setTestOnBorrow(config.getBoolean("connection-pool(" + i + ").test-on-borrow", true));
			}
			//
			if (RedisConfigType.SINGLE.equals(redisConfigType) && ProviderConfigType.LETTUCE.equals(providerConfigType)) {
				int timeout = config.getInt("connection-pool(" + i + ").timeout", 5000);
				Set<RedisURI> redisServerSet = new HashSet<>();
				String host = config.getString("connection-pool(" + i + ").server.host");
				int port = config.getInt("connection-pool(" + i + ").server.port");
				redisServerSet.add(new RedisURI(host, port, Duration.ofMillis(timeout)));

				RedisClient lettuceRedisClient = RedisClient.create(new RedisURI(host, port, Duration.ofMillis(timeout)));
				// connection, 线程安全的长连接，连接丢失时会自动重连，直到调用 close 关闭连接。
				StatefulRedisConnection<String, String> connection = lettuceRedisClient.connect();
				pools.put(poolName, new JedisPoolWraper<StatefulRedisConnection<String, String>, RedisURI, AbstractRedisClient>(poolName, connection, usePool, redisConfigType, providerConfigType, poolConfig, redisServerSet, lettuceRedisClient));

			} else if (RedisConfigType.CLUSTER.equals(redisConfigType) && ProviderConfigType.LETTUCE.equals(providerConfigType)) {

				int timeout = config.getInt("connection-pool(" + i + ").timeout", 5000);
				int serversQty = config.getMaxIndex("connection-pool(" + i + ").servers.server") + 1;
				log.trace("ServersQty:{}", serversQty);
				Set<RedisURI> redisServerSet = new HashSet<>();
				for (int m = 0; m < serversQty; m++) {
					String host = config.getString("connection-pool(" + i + ").servers.server(" + m + ").host");
					int port = config.getInt("connection-pool(" + i + ").servers.server(" + m + ").port");
					redisServerSet.add(new RedisURI(host, port, Duration.ofMillis(timeout)));
				}
				RedisClusterClient clusterClient = RedisClusterClient.create(redisServerSet);
				if (usePool) {
					GenericObjectPool<StatefulRedisClusterConnection<String, String>> lettucePool = //
							ConnectionPoolSupport.createGenericObjectPool(() -> clusterClient.connect(), poolConfig);
					pools.put(poolName, new JedisPoolWraper<GenericObjectPool<StatefulRedisClusterConnection<String, String>>, RedisURI, AbstractRedisClient>(poolName, lettucePool, usePool, redisConfigType, providerConfigType, poolConfig, redisServerSet, clusterClient));
				} else {
					StatefulRedisClusterConnection<String, String> conn = clusterClient.connect();
					pools.put(poolName, new JedisPoolWraper<StatefulRedisClusterConnection<String, String>, RedisURI, AbstractRedisClient>(poolName, conn, usePool, redisConfigType, providerConfigType, poolConfig, redisServerSet, clusterClient));
				}

			} else if (RedisConfigType.SINGLE.equals(redisConfigType) && ProviderConfigType.JEDIS.equals(providerConfigType)) {
				Set<HostAndPort> redisServerSet = new HashSet<>();
				String host = config.getString("connection-pool(" + i + ").server.host");
				int port = config.getInt("connection-pool(" + i + ").server.port");
				redisServerSet.add(new HostAndPort(host, port));

				int timeout = config.getInt("connection-pool(" + i + ").timeout", 5000);
				JedisPool jedisPool = new JedisPool(poolConfig, host, port, timeout);// ,AUTH

				pools.put(poolName, new JedisPoolWraper<JedisPool, HostAndPort, Void>(poolName, jedisPool, usePool, redisConfigType, providerConfigType, poolConfig, redisServerSet));

			} else if (RedisConfigType.CLUSTER.equals(redisConfigType) && ProviderConfigType.JEDIS.equals(providerConfigType)) {

				int serversQty = config.getMaxIndex("connection-pool(" + i + ").servers.server") + 1;
				log.trace("ServersQty:{}", serversQty);
				Set<HostAndPort> redisServerSet = new HashSet<>();
				for (int m = 0; m < serversQty; m++) {
					String host = config.getString("connection-pool(" + i + ").servers.server(" + m + ").host");
					int port = config.getInt("connection-pool(" + i + ").servers.server(" + m + ").port");
					redisServerSet.add(new HostAndPort(host, port));
				}

				int timeout = config.getInt("connection-pool(" + i + ").timeout", 5000);
				int maxAttempts = config.getInt("connection-pool(" + i + ").max-attempts", 1);
				JedisCluster jedisCluster = new JedisCluster(redisServerSet, timeout, timeout, maxAttempts, poolConfig);
				pools.put(poolName, new JedisPoolWraper<JedisCluster, HostAndPort, Void>(poolName, jedisCluster, usePool, redisConfigType, providerConfigType, poolConfig, redisServerSet));

			} else {
				throw new IllegalArgumentException("Unkown Pool Type=" + poolType);
			}
		}
		if (!pools.containsKey(DEFAULT_POOL_NAME)) {
			DEFAULT_POOL_NAME = null;
			throw new IllegalArgumentException("pools not contains DEFAULT_POOL_NAME[" + DEFAULT_POOL_NAME + "]");
		}
		TwoTuple<String, Map<String, JedisPoolWraper>> twoTuple = new TwoTuple<>(DEFAULT_POOL_NAME, pools);
		return twoTuple;
	}

	private static TwoTuple<String, Map<String, JedisPoolWraper>> initConfigV2_0_0(XMLConfiguration config) {
		String DEFAULT_POOL_NAME = null;
		Map<String, JedisPoolWraper> pools = new HashMap<>();
		String deffaultPoolName = config.getString("default-connection-pool");
		if (StringUtils.isNotBlank(deffaultPoolName)) {
			DEFAULT_POOL_NAME = deffaultPoolName;
		}
		int connPoolQty = config.getMaxIndex("connection-pool") + 1;
		log.trace("ConnPoolQty:{}", connPoolQty);
		if (connPoolQty <= 0) {
			DEFAULT_POOL_NAME = null;
			throw new RuntimeException("connection-pool is null");
		}

		for (int i = 0; i < connPoolQty; i++) {
			String poolName = config.getString("connection-pool(" + i + ")[@name]", "");
			if (StringUtils.isBlank(poolName)) {
				clearPool(pools);
				throw new IllegalArgumentException("poolName can not be empty");
			}
			if (pools.containsKey(poolName)) {
				clearPool(pools);
				throw new IllegalArgumentException("duplicated poolName in config :" + poolName);
			}
			if (i == 0 && StringUtils.isBlank(DEFAULT_POOL_NAME)) {
				DEFAULT_POOL_NAME = poolName;
			}
			//
			String poolType = config.getString("connection-pool(" + i + ")[@type]", "");
			if (StringUtils.isBlank(poolType)) {// 2.0.0版本以下不配置, 继续查找connection-pool.pool-type
				poolType = config.getString("connection-pool(" + i + ").pool-type", "");
			}
			RedisConfigType redisConfigType = RedisConfigType.getFromType(poolType);
			//
			String poolProvider = config.getString("connection-pool(" + i + ")[@provider]", "");
			if (StringUtils.isBlank(poolProvider)) {// 2.0.0版本以下不配置, 默认JEDIS
				poolProvider = "JEDIS";
			}
			ProviderConfigType providerConfigType = ProviderConfigType.getFromType(poolProvider);
			//
			boolean usePool = true;
			//
			GenericObjectPoolConfig<?> poolConfig = null;
			if (usePool) {
				poolConfig = new GenericObjectPoolConfig<>();
				poolConfig.setMaxTotal(config.getInt("connection-pool(" + i + ").max-active", -1));
				poolConfig.setMinIdle(config.getInt("connection-pool(" + i + ").min-idle", 5));
				poolConfig.setMaxIdle(config.getInt("connection-pool(" + i + ").max-idle", 10));
				poolConfig.setMaxWaitMillis(config.getInt("connection-pool(" + i + ").max-wait", 5000));
				poolConfig.setTestOnBorrow(config.getBoolean("connection-pool(" + i + ").test-on-borrow", true));
			}
			//
			if (RedisConfigType.SINGLE.equals(redisConfigType) && ProviderConfigType.JEDIS.equals(providerConfigType)) {
				Set<HostAndPort> redisServerSet = new HashSet<>();
				String host = config.getString("connection-pool(" + i + ").server.host");
				int port = config.getInt("connection-pool(" + i + ").server.port");
				redisServerSet.add(new HostAndPort(host, port));

				int timeout = config.getInt("connection-pool(" + i + ").timeout", 5000);
				JedisPool jedisPool = new JedisPool(poolConfig, host, port, timeout);// ,AUTH

				pools.put(poolName, new JedisPoolWraper<JedisPool, HostAndPort, Void>(poolName, jedisPool, usePool, redisConfigType, providerConfigType, poolConfig, redisServerSet));

			} else if (RedisConfigType.CLUSTER.equals(redisConfigType) && ProviderConfigType.JEDIS.equals(providerConfigType)) {

				int serversQty = config.getMaxIndex("connection-pool(" + i + ").servers.server") + 1;
				log.trace("ServersQty:{}", serversQty);
				Set<HostAndPort> redisServerSet = new HashSet<>();
				for (int m = 0; m < serversQty; m++) {
					String host = config.getString("connection-pool(" + i + ").servers.server(" + m + ").host");
					int port = config.getInt("connection-pool(" + i + ").servers.server(" + m + ").port");
					redisServerSet.add(new HostAndPort(host, port));
				}

				int timeout = config.getInt("connection-pool(" + i + ").timeout", 5000);
				int maxAttempts = config.getInt("connection-pool(" + i + ").max-attempts", 1);
				JedisCluster jedisCluster = new JedisCluster(redisServerSet, timeout, timeout, maxAttempts, poolConfig);
				pools.put(poolName, new JedisPoolWraper<JedisCluster, HostAndPort, Void>(poolName, jedisCluster, usePool, redisConfigType, providerConfigType, poolConfig, redisServerSet));

			} else {
				throw new IllegalArgumentException("Unkown poolType:" + redisConfigType + ", poolProvider:" + providerConfigType);
			}
		}
		if (!pools.containsKey(DEFAULT_POOL_NAME)) {
			DEFAULT_POOL_NAME = null;
			throw new IllegalArgumentException("pools not contains DEFAULT_POOL_NAME[" + DEFAULT_POOL_NAME + "]");
		}
		TwoTuple<String, Map<String, JedisPoolWraper>> twoTuple = new TwoTuple<>(DEFAULT_POOL_NAME, pools);
		return twoTuple;
	}

	public static synchronized void clearPool(Map<String, JedisPoolWraper> pools) {
		try {
			Set<String> keySet = pools.keySet();
			String[] keyArray = new String[keySet.size()];
			int idx = 0;
			for (String tmpKey : keySet) {
				keyArray[idx++] = tmpKey;
			}

			for (String tmpKey : keyArray) {
				String poolName = tmpKey;
				log.warn("shutdown pool[{}]...", poolName);
				JedisPoolWraper jedisPoolWraper = pools.remove(poolName);
				if (jedisPoolWraper != null) {
					try {
						if (ProviderConfigType.LETTUCE.equals(jedisPoolWraper.getProvider())) {
							if (RedisConfigType.SINGLE.equals(jedisPoolWraper.getType())) {// LETTUCE单机
								StatefulRedisConnection<String, String> connection = (StatefulRedisConnection<String, String>) jedisPoolWraper.getPool();
								connection.close();
								RedisClient lettuceRedisClient = (RedisClient) jedisPoolWraper.getAppendObj();
								lettuceRedisClient.shutdown();

							} else if (RedisConfigType.CLUSTER.equals(jedisPoolWraper.getType())) {// LETTUCE集群
								if (jedisPoolWraper.isUsePool()) {
									GenericObjectPool<StatefulRedisClusterConnection<String, String>> pool = (GenericObjectPool<StatefulRedisClusterConnection<String, String>>) jedisPoolWraper.getPool();
									pool.close();
								}
								RedisClusterClient clusterClient = (RedisClusterClient) jedisPoolWraper.getAppendObj();
								clusterClient.shutdown();

							}
						} else if (ProviderConfigType.JEDIS.equals(jedisPoolWraper.getProvider())) {
							if (RedisConfigType.SINGLE.equals(jedisPoolWraper.getType())) {// JEDIS单机
								JedisPool jedisPool = (JedisPool) jedisPoolWraper.getPool();
								jedisPool.close();

							} else if (RedisConfigType.CLUSTER.equals(jedisPoolWraper.getType())) {// JEDIS集群
								JedisCluster jedisCluster = (JedisCluster) jedisPoolWraper.getPool();
								jedisCluster.close();

							}
						}
					} catch (Throwable e) {
						log.error("Close Pool Error key={}", poolName, e);
					}
				}
				jedisPoolWraper = null;
			}
		} finally {
			pools = null;
		}
	}

	public static void main(String[] args) throws Exception {

		RedisConfigHelper.initConfig();
		log.trace("SLEEP");

		TimeUnit.SECONDS.sleep(1);
		log.trace("EXIT");
	}

}
